import sys
import time
import socket
import threading
from PyQt5 import QtCore, QtGui, QtWidgets
from gobang_ui import Ui_MainWindow  # 导入由Qt Designer生成的UI类

# 定义一个继承自QMainWindow和Ui_MainWindow的客户端类
class WhiteClient(QtWidgets.QMainWindow, Ui_MainWindow):
    # 定义信号，用于线程间通信，更新界面等
    update_board_signal = QtCore.pyqtSignal(int, int, int)  # 更新棋盘的信号
    update_labels_signal = QtCore.pyqtSignal()  # 更新标签的信号
    show_win_signal = QtCore.pyqtSignal(str)  # 显示胜利信息的信号
    chat_message_signal = QtCore.pyqtSignal(str)  # 显示聊天消息的信号

    def __init__(self):
        super().__init__()  # 调用父类构造函数
        self.setupUi(self)  # 设置UI组件

        # 初始化游戏参数
        self.board_size = 15  # 棋盘大小（15x15）
        self.grid_size = 66  # 网格尺寸
        self.board = [[0 for _ in range(self.board_size)] for _ in range(self.board_size)]  # 棋盘状态初始化
        self.current_player = 1  # 当前玩家，1代表黑方，2代表白方
        self.steps = 0  # 步数计数器
        self.start_time = None  # 游戏开始时间戳
        self.socket = None  # 服务器连接套接字
        self.running = True  # 运行标志
        self.player_id = 1  # 玩家ID，默认为1（黑方）

        # 确保按回车键或点击发送按钮都可以发送消息
        self.sendButton.clicked.connect(self.sendMessage)  # 发送按钮点击事件绑定
        self.chatLineEdit.returnPressed.connect(self.sendMessage)  # 回车键发送消息事件绑定

        # 重写paintEvent方法以绘制棋盘
        self.chessboard.paintEvent = self.paintChessboard

        # 将信号与槽函数连接起来
        self.update_board_signal.connect(self.updateBoard)
        self.update_labels_signal.connect(self.updateLabels)
        self.show_win_signal.connect(self.showWinMessage)
        self.chat_message_signal.connect(self.appendChatMessage)

        # 连接到服务器
        self.connectToServer("172.20.10.4")

    # 添加聊天消息到聊天框
    def appendChatMessage(self, message):
        self.chatTextBrowser.append(message)

    # 连接到服务器的方法
    def connectToServer(self, host_ip):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建TCP/IP套接字
        try:
            self.socket.connect((host_ip, 12345))  # 尝试连接到服务器
            self.chatTextBrowser.append("已连接到服务器")  # 连接成功提示
            threading.Thread(target=self.receiveMessages).start()  # 启动接收消息的线程
        except Exception as e:
            self.chatTextBrowser.append(f"连接失败: {e}")  # 连接失败提示

    # 开始游戏的方法
    def startGame(self):
        self.start_time = time.time()  # 记录游戏开始时间
        self.chatTextBrowser.append("游戏开始！")  # 提示游戏开始
        self.label_7.setText(f"轮到 {'黑方' if self.current_player == 1 else '白方'}！")  # 更新当前玩家标签

    # 鼠标按下事件处理，用于放置棋子
    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton and self.chessboard.geometry().contains(event.pos()):
            x = int(event.pos().x() / self.grid_size)  # 计算鼠标点击的X坐标对应的棋盘位置
            y = int(event.pos().y() / self.grid_size)  # 计算鼠标点击的Y坐标对应的棋盘位置
            if 0 <= x < self.board_size and 0 <= y < self.board_size:
                if self.board[y][x] == 0 and self.current_player == self.player_id:
                    print(f"Mouse press at ({x}, {y})")  # 打印日志
                    self.board[y][x] = self.current_player  # 放置棋子
                    self.steps += 1  # 增加步数
                    self.update_labels_signal.emit()  # 更新标签
                    self.update_board_signal.emit(x, y, self.current_player)  # 更新棋盘显示
                    if self.checkWin(x, y, self.current_player):  # 检查是否赢了
                        pass  # 如果赢了，则不切换玩家也不发送移动
                    else:
                        self.current_player = 3 - self.current_player  # 切换玩家
                        self.label_7.setText(f"轮到 {'黑方' if self.current_player == 1 else '白方'}！")  # 更新标签
                        self.sendMove(x, y)  # 发送移动给对方

    # 发送移动给对方
    def sendMove(self, x, y):
        move_message = f"MOVE {x} {y}\n"  # 构造移动消息
        try:
            self.socket.sendall(move_message.encode('utf-8'))  # 发送消息给服务器
            self.chatTextBrowser.append(f"发送移动: {move_message.strip()}")  # 记录发送的消息
        except BrokenPipeError as e:
            self.chatTextBrowser.append(f"发送失败: {e}")  # 发送失败提示

    # 接收来自服务器的消息
    def receiveMessages(self):
        buffer = ""  # 用于存储接收到的数据
        self.victory_detected = False  # 胜利检测标志
        self.new_move_received = False  # 新移动接收标志

        while self.running:  # 持续监听服务器消息
            try:
                data = self.socket.recv(1024).decode('utf-8')  # 接收数据并解码
                if not data:
                    break  # 如果没有数据则退出循环
                buffer += data  # 将接收到的数据追加到缓冲区
                while "\n" in buffer:  # 处理完整的消息
                    line, buffer = buffer.split("\n", 1)  # 分割出一条完整消息

                    if line.startswith("PLAYER_ID"):  # 如果是玩家ID消息
                        _, player_id = line.split()
                        self.player_id = int(player_id)  # 设置玩家ID
                        self.current_player = self.player_id  # 设置当前玩家
                        self.label_7.setText(f"你是 {'黑方' if self.player_id == 1 else '白方'}！")  # 更新标签

                    elif line.startswith("GAME_START"):  # 如果是游戏开始消息
                        self.startGame()  # 开始游戏

                    elif line.startswith("MOVE"):  # 如果是移动消息
                        self.new_move_received = True  # 标记接收到新移动
                        self.victory_detected = False  # 重置胜利标志

                        _, x, y = line.split()  # 解析移动消息
                        x, y = int(x), int(y)
                        if self.board[y][x] == 0:  # 如果该位置为空
                            self.board[y][x] = 3 - self.player_id  # 在棋盘上放置对手的棋子
                            self.steps += 1  # 增加步数
                            self.update_labels_signal.emit()  # 更新标签
                            self.update_board_signal.emit(x, y, 3 - self.player_id)  # 更新棋盘显示
                            if self.checkWin(x, y, 3 - self.player_id):  # 检查是否赢了
                                pass  # 如果赢了，则不切换玩家也不发送移动
                            else:
                                self.current_player = self.player_id  # 切换玩家
                                self.label_7.setText(f"轮到 {'黑方' if self.current_player == 1 else '白方'}！")  # 更新标签

                    elif line.startswith("CHAT"):  # 如果是聊天消息
                        message = line[5:].strip()  # 获取聊天内容
                        self.chat_message_signal.emit(message)  # 发送聊天内容信号
                        if "游戏结束" in message:  # 如果是游戏结束的消息，则触发胜利处理
                            self.showWinMessage("白方" if "白方" in message else "黑方")
                            QtCore.QTimer.singleShot(2000, self.resetIfNoNewMove)  # 2秒后检查是否重置

            except ConnectionResetError as e:
                self.chatTextBrowser.append(f"接收失败: {e}")  # 接收失败提示
                break  # 退出循环

    # 发送聊天消息
    def sendMessage(self):
        message = self.chatLineEdit.text().strip()  # 获取输入框中的消息
        if message:  # 如果有消息
            # 暂时禁用发送按钮和回车键发送功能
            self.sendButton.setEnabled(False)
            self.chatLineEdit.setReadOnly(True)

            timestamp = time.strftime("%H:%M:%S", time.localtime())  # 获取当前时间
            formatted_message = f"CHAT [{timestamp}] 用户: {message}\n"  # 构造聊天消息格式
            try:
                self.socket.sendall(formatted_message.encode('utf-8'))  # 发送消息给服务器
                self.appendChatMessage(f"[{timestamp}] 你: {message}")  # 直接在本地添加自己的消息
            except (BrokenPipeError, ConnectionError) as e:
                self.appendChatMessage(f"发送失败: {e}")  # 发送失败提示
            finally:
                self.chatLineEdit.clear()  # 清空输入框以防止重复发送

            # 在短延迟后重新启用发送功能
            QtCore.QTimer.singleShot(300, lambda: (
                self.sendButton.setEnabled(True),
                self.chatLineEdit.setReadOnly(False)
            ))
    # 更新标签，如步数和时间
    def updateLabels(self):
        elapsed_time = time.time() - self.start_time if self.start_time else 0  # 计算经过的时间
        minutes, seconds = divmod(int(elapsed_time), 60)  # 将时间转换为分钟和秒
        self.label_2.setText(f"步数：{self.steps}")  # 更新步数标签
        self.label_3.setText(f"时间：{minutes:02}:{seconds:02}")  # 更新时间标签

    # 更新棋盘显示
    def updateBoard(self, x, y, player):
        self.chessboard.update()  # 触发棋盘重绘

    # 绘制棋盘
    def paintChessboard(self, event):
        painter = QtGui.QPainter(self.chessboard)  # 创建画家对象
        painter.setRenderHint(QtGui.QPainter.Antialiasing)  # 设置抗锯齿
        pen = QtGui.QPen(QtGui.QColor("#000000"), 2)  # 设置画笔颜色和宽度
        painter.setPen(pen)

        for i in range(self.board_size):  # 绘制网格线
            painter.drawLine(i * self.grid_size, 0, i * self.grid_size, self.board_size * self.grid_size)
            painter.drawLine(0, i * self.grid_size, self.board_size * self.grid_size, i * self.grid_size)

        for y in range(self.board_size):  # 绘制棋子
            for x in range(self.board_size):
                if self.board[y][x] == 1:  # 黑棋
                    painter.setBrush(QtGui.QBrush(QtGui.QColor("#000000")))
                    painter.drawEllipse(x * self.grid_size + 10, y * self.grid_size + 10, 46, 46)
                elif self.board[y][x] == 2:  # 白棋
                    painter.setBrush(QtGui.QBrush(QtGui.QColor("#FFFFFF")))
                    painter.drawEllipse(x * self.grid_size + 10, y * self.grid_size + 10, 46, 46)

    # 检查是否有玩家获胜
    def checkWin(self, x, y, player):
        directions = [(-1, 0), (0, -1), (-1, -1), (-1, 1)]  # 定义四个方向
        for dx, dy in directions:
            count = 1  # 初始化计数器
            for i in range(1, 5):  # 检查正向连续棋子数量
                nx, ny = x + dx * i, y + dy * i
                if 0 <= nx < self.board_size and 0 <= ny < self.board_size and self.board[ny][nx] == player:
                    count += 1
                else:
                    break
            for i in range(1, 5):  # 检查反向连续棋子数量
                nx, ny = x - dx * i, y - dy * i
                if 0 <= nx < self.board_size and 0 <= ny < self.board_size and self.board[ny][nx] == player:
                    count += 1
                else:
                    break
            if count >= 5:  # 如果连续棋子数量达到或超过5个，则有人获胜
                winner = "白方" if player == 2 else "黑方"
                self.show_win_signal.emit(winner)  # 触发胜利信息显示
                # 延迟2秒后检查是否重置
                QtCore.QTimer.singleShot(2000, self.resetIfNoNewMove)
                return True
        return False

    # 如果在一段时间内没有新的移动，则重置游戏
    def resetIfNoNewMove(self):
        if not self.new_move_received:  # 如果在这段时间内没有收到新的移动
            self.resetGame()  # 重置游戏
        self.victory_detected = False  # 重置胜利标志
        self.new_move_received = False  # 重置新移动接收标志

    # 显示胜利信息
    def showWinMessage(self, winner):
        QtWidgets.QMessageBox.information(self, "游戏结束", f"{winner}获胜！")  # 弹出胜利信息对话框
        self.chatTextBrowser.append(f"游戏结束: {winner}获胜！")  # 将胜利信息添加到聊天框

        # 也向对方发送胜利消息，确保双方都收到通知
        timestamp = time.strftime("%H:%M:%S", time.localtime())
        win_message = f"CHAT [{timestamp}] 系统: 游戏结束, {winner}获胜！\n"
        try:
            self.socket.sendall(win_message.encode('utf-8'))  # 发送胜利消息给对方
        except (BrokenPipeError, ConnectionError) as e:
            self.chatTextBrowser.append(f"发送失败: {e}")  # 发送失败提示

        # 启动延迟重置计时器
        QtCore.QTimer.singleShot(2000, self.resetIfNoNewMove)

    # 添加聊天消息到聊天框
    def appendChatMessage(self, message):
        self.chatTextBrowser.append(message)

    # 定义重置游戏的方法
    def resetGame(self):
        # 重置棋盘为初始状态（所有位置都设为0）
        self.board = [[0 for _ in range(self.board_size)] for _ in range(self.board_size)]
        # 将步数计数器重置为0
        self.steps = 0
        # 清除游戏开始时间戳
        self.start_time = None
        # 设置当前玩家为自己的ID（即恢复到初始玩家）
        self.current_player = self.player_id
        # 更新标签以显示当前玩家信息
        self.label_7.setText(f"你是 {'黑方' if self.player_id == 1 else '白方'}！")
        # 触发棋盘重绘，确保界面上的棋子被清除
        self.chessboard.update()
        # 更新标签，如步数和时间
        self.updateLabels()


if __name__ == "__main__":
    # 如果此文件作为主程序运行，则执行以下代码

    # 创建一个Qt应用程序实例
    app = QtWidgets.QApplication(sys.argv)

    # 创建WhiteClient类的一个实例，即游戏窗口
    window = WhiteClient()

    # 显示游戏窗口
    window.show()

    # 进入应用程序的主事件循环，并在退出时返回系统退出码
    sys.exit(app.exec_())